﻿namespace Groundwork.Extensions
{
    using System;
    using System.IO;
    using System.Text;
    using System.Xml;
    using System.Xml.Linq;
    using System.Xml.Serialization;

    /// <summary>
    /// Xml Extension methods
    /// </summary>
    public static class Xml
    {
        /// <summary>
        /// converts the provided value into a valid xml document
        /// </summary>
        /// <param name="value">value to use as the XDocument</param>
        /// <returns>XDocument output</returns>
        /// <exception cref="System.ArgumentNullException">Throws if value is null or empty</exception>
        public static XDocument ToXDocument(this string value)
        {
            value.ThrowIfNullOrEmpty();
            try
            {
                XDocument document = XDocument.Parse(value);
                return document;
            }
            catch 
            {
                throw;
            }
        }

        /// <summary>
        /// Coverts the provided string to a valid XmlDocument
        /// </summary>
        /// <param name="value">string to convert to Xml document</param>
        /// <returns>Valid Xml Document</returns>
        /// <exception cref="System.ArgumentNullException">Throws if value is null or empty</exception>
        public static XmlDocument ToXmlDocument(this string value)
        {
            value.ThrowIfNullOrEmpty();
            try
            {
                XmlDocument document = new XmlDocument();
                document.LoadXml(value);
                return document;
            }
            catch
            {
                throw;
            }
        }

        /// <summary>
        /// Unbinds the object to an xml string representation
        /// </summary>
        /// <typeparam name="T">Type to unbind</typeparam>
        /// <param name="value">current string value</param>
        /// <param name="encoding">Specifies the encoding for the output Xml document. If null the default is UTF-8</param>
        /// <returns>xml string</returns>
        /// <exception cref="System.ArgumentNullException">Throws if value is null or empty</exception>
        public static string XmlSerialize<T>(this T value, Encoding encoding = null) where T : class, new()
        {
            value.ThrowIfNull();
            Encoding convertToEncoding = encoding == null ? Encoding.UTF8 : encoding;

            var serializer = new XmlSerializer(typeof(T));
            try
            {
                using (var writer = new StringWriter())
                {
                    serializer.Serialize(writer, value);
                    string xmlString = writer.ToString();
                    XmlDocument doc = new XmlDocument();
                    try
                    {
                        doc.LoadXml(xmlString);
                    }
                    catch 
                    {
                        throw;
                    }

                    CorrectEncoding(ref doc, convertToEncoding.BodyName);
                    return doc.OuterXml;
                }
            }
            catch 
            {
                throw;
            }
        }

        /// <summary>
        /// Binds the provided string to a model
        /// </summary>
        /// <typeparam name="T">type to bind</typeparam>
        /// <param name="value">value to bind</param>
        /// <returns>string bound model</returns>
        /// <exception cref="System.ArgumentNullException">Throws if value is null or empty</exception>
        public static T XmlDeserialize<T>(this string value) where T : class, new()
        {
            value.ThrowIfNull();

            var serializer = new XmlSerializer(typeof(T));
            using (var reader = new StringReader(value))
            {
                try
                {
                    return (T)serializer.Deserialize(reader);
                }
                catch
                {
                    return null;
                }
            }
        }

        /// <summary>
        /// Removes the encoding from the xml string to prevent it breaking elsewhere
        /// </summary>
        /// <param name="obj">Xml Document object to be altered</param>
        /// <param name="encoding">character encoding</param>
        private static void CorrectEncoding(ref XmlDocument obj, string encoding)
        {
            XmlDeclaration decl = (XmlDeclaration)obj.FirstChild;
            if (decl != null)
            {
                decl.Encoding = encoding;
            }
        }
    }
}
